#define _USE_MATH_DEFINES

#include <jni.h>
#include <GLES/gl.h>
#include <stdio.h>
#include <math.h>
#include <png.h>
#include <errno.h>
#include <malloc.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "../common/app.h"
#include "../common/pic.h"
#include "../common/log.h"
#include "tfw.h"

struct point map_px = {0, 0};
struct point screen_px = {0, 0};
struct point mouse_px = {0, 0};
struct point mouse_sc = {0, 0};
struct point mouse_down_sc = {0, 0};
int mouse_down = 0;
struct point divider = {0, 0};
struct point viewport = {0, 0};

const char* g_appname = "pngtest";
char base_name[1024];
char png_name[1024];
char tfw_name[1024];
int g_log_level = 0;
int draw_grid = 0;

struct point scr_nw = {-1.5, -1.0};
struct point scr_se = {1.5, 1.0};
//struct point scr_offset = {-2.3, -3.5};
struct point scr_offset = {-0.8, -0.4};
//float scale = 0.5f;
double scale = 1.5;
struct tfw tfw = {0};
struct coord_convertor* cconv = NULL;

struct marker
{
	struct point utm;
	struct point px;
	struct point sc;
};

struct marker markers[3] = {
	{ {0, 0}, {1000, 1000}, {0, 0} },
	{ {0, 0}, {6000, 1000}, {0, 0} },
	{ {0, 0}, {6000, 7000}, {0, 0} },
};

struct point zero_utm = {0, 0};

struct pic pic_font;
struct pic tiles[16];
const int n_tiles = sizeof(tiles) / sizeof(tiles[0]);

struct map_info
{
	struct image_info img;
};

struct map_info map = { {7200, 8700} };

static void gl_init()
{
	GLint params[2] = {0};
	glGetIntegerv(GL_MAX_TEXTURE_SIZE, params);

	LOGI("GL_MAX_TEXTURE_SIZE: %d,%d", params[0], params[1]);
	glEnable(GL_ALPHA_TEST);
	glAlphaFunc(GL_GREATER, 0.5f);
	glDisable(GL_DITHER);
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);
	glEnable(GL_DEPTH_TEST);
	glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
	glShadeModel(GL_SMOOTH);
	glEnable(GL_BLEND);
}

static void gl_set_map_mode()
{
	glViewport(0, 0, (GLsizei)viewport.x, (GLsizei)viewport.y);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glOrthof(scr_nw.x, scr_se.x, scr_nw.y, scr_se.y, -1, 1);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
}

static void gl_set_terminal_mode()
{
	glViewport(0, 0, (GLsizei)viewport.x, (GLsizei)viewport.y);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glOrthof(0, viewport.x, viewport.y, 0, -1, 1);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
}

struct strip
{
	char name[20];
	size_t n;
	float v[1];
};

struct strip* strip;

static void parse_cmd_line()
{
	char* p;
	LPWSTR s = GetCommandLineW();
	int argc = 0;
	LPWSTR* argv = CommandLineToArgvW(s, &argc);
	if (argc < 2)
		return;
	sprintf(base_name, "%S", argv[1]);
	p = strrchr(base_name, '.');
	*p = 0;

	sprintf(tfw_name, "%s.tfw", base_name);
	sprintf(png_name, "%s.png", base_name);
}

static void draw_test_vectors()
{
	glColor3b(80, 0, 0);
	glEnableClientState(GL_VERTEX_ARRAY);
 
	glVertexPointer(2, GL_FLOAT, 0, strip->v);
	glDrawArrays(GL_LINE_STRIP, 0, strip->n);
 
	glDisableClientState(GL_VERTEX_ARRAY);

}

static void load_mvg()
{
	char s[1024];
	int n = 100, scanned, x, y, idx = 0, found = 0;

	FILE* f = fopen("o41074b2.mvg", "rt");
	if (f == NULL)
	{
		LOGE("cannot open mvg file");
	}

	while (!feof(f))
	{
		fgets(s, 1024, f);
		if (strncmp(s, "  polyline", 10) == 0)
		{
			strip = malloc(sizeof(struct strip) + n*sizeof(float));
			strcpy(strip->name, "test");
			strip->n = 0;
			found = 1;
			while (1)
			{
				scanned = fscanf(f, " %d,%d", &x, &y);
				if (scanned == 0)
					break;

//				tfw_utm_to_px(&tfw, x, y, &x, &y);
				strip->v[idx++] = (float)x;
				strip->v[idx++] = (float)y;
				strip->n++;
				if (idx >= n)
				{
					n = n*2;
					strip = realloc(strip, sizeof(struct strip) + n*sizeof(float));
				}
			}
		}
		else if (found == 1)
			break;
	}
}

static void load_markers()
{
	FILE* f;
	int n, i, d;
	char fname[1024];
	float x1, y1, x2, y2, x3, y3;

	sprintf(fname, "%s.markers", base_name);

	f = fopen(fname, "rt");
	if (f == NULL)
		return;

	// marker 1: utm: 563000.000000, 4566000.000000, px: 1096.845317, 1134.675712, screen: 0.609359, 0.630375

	for (i = 0; i < 3; i++)
	{
		n = fscanf(f, "marker %d: utm: %e, %e, px: %e, %e, screen: %e, %e\n", 
			&d, &x1, &y1, &x2, &y2, &x3, &y3);

		markers[i].utm.x = x1;
		markers[i].utm.y = y1;
		markers[i].px.x = x2;
		markers[i].px.y = y2; 
		markers[i].sc.x = x3;
		markers[i].sc.y = y3;

		if (n != 7)
		{
			LOGE("invalid record %d in markers file", i);
		}
	}

	fclose(f);
}

void app_init(int w, int h)
{
	viewport.x = w;
	viewport.y = h;

	parse_cmd_line();
	tfw_read(&tfw, tfw_name);
	load_markers();
	pic_load_png(&pic_font, 1, "font.png", NULL);
//	pic_load_png(tiles, 4, png_name, &map.img);
//	pic_load_mapped_png(tiles, 4, "sprite.png", &map.img);
	gl_init();

	screen_px.x = (float)w;
	screen_px.y = (float)h;
	
	divider.x = screen_px.x / (scr_se.x - scr_nw.x) * scale;
	divider.y = screen_px.y / (scr_se.y - scr_nw.y) * scale;
}

void draw_lines()
{
	glLineWidth(1);
	glBegin(GL_LINES);

	// x, red, right
	glColor3b(80, 0, 0);
	glVertex3i(0, 0, 0);
	glVertex3i(5, 0, 0);

	// y, green, down
	glColor3b(0, 80, 0);
	glVertex3i(0, 0, 0);
	glVertex3i(0, 5, 0);

	// -x, blue, left
	glColor3b(0, 0, 80);
	glVertex3i(0, 0, 0);
	glVertex3i(-1, 0, 0);

	// -y, magenta, top
	glColor3b(80, 0, 80);
	glVertex3i(0, 0, 0);
	glVertex3i(0, -1, 0);

	// x=1
	glColor3b(120, 0, 0);
	glVertex3f(1, 0.1f, 0);
	glVertex3f(1, -0.1f, 0);

	// y=1
	glColor3b(0, 120, 0);
	glVertex3f(0.1f, 1, 0);
	glVertex3f(-0.1f, 1, 0);

	glEnd();

	glLineWidth(2);

	glBegin(GL_LINES);

	// from marker[0] to marker[1]
	glColor3b(80, 0, 80);
	glVertex3d(markers[0].sc.x, markers[0].sc.y, 0);
	glVertex3d(markers[1].sc.x, markers[1].sc.y, 0);

	// from marker[1] to marker[2]
	glColor3b(80, 0, 80);
	glVertex3d(markers[1].sc.x, markers[1].sc.y, 0);
	glVertex3d(markers[2].sc.x, markers[2].sc.y, 0);
	
	glEnd();
}

void draw_utm_grid()
{
	double start, end, sx, sy;
	int x, y;
	double aspect = (double)map.img.height / (double)map.img.width;

	if (!draw_grid)
		return;

	glLineWidth(1.5);
	glBegin(GL_LINES);
	glColor3b(0, 80, 10);

	// vertical lines

	start = markers[0].utm.x / 1000 * 1000;
	end = markers[1].utm.x / 1000 * 1000 + 1000;

	while (start < end)
	{
		tfw_utm_to_px(&tfw, start, markers[1].utm.y + 1000, &x, &y);
		sx = 4.0 * x / (double)map.img.width;
		sy = 4.0 * aspect * y / (double)map.img.height;
		glVertex3d(sx, sy, 0);

		tfw_utm_to_px(&tfw, start, markers[2].utm.y - 1000, &x, &y);
		sx = 4.0 * x / (double)map.img.width;
		sy = 4.0 * aspect * y / (double)map.img.height;
		glVertex3d(sx, sy, 0);

		start += 1000;
	}

	// horizontal lines

	start = markers[2].utm.y / 1000 * 1000 - 1000;
	end = markers[0].utm.y / 1000 * 1000 + 1000;

	while (start < end)
	{
		tfw_utm_to_px(&tfw, markers[0].utm.x, start, &x, &y);
		sx = 4.0 * x / (double)map.img.width;
		sy = 4.0 * aspect * y / (double)map.img.height;
		glVertex3d(sx, sy, 0);

		tfw_utm_to_px(&tfw, markers[1].utm.x, start, &x, &y);
		sx = 4.0 * x / (double)map.img.width;
		sy = 4.0 * aspect * y / (double)map.img.height;
		glVertex3d(sx, sy, 0);

		start += 1000;
	}

	glEnd();
}

void draw_char(char c)
{
	int column = c % 16, row = c / 16;
	float x, y, inc = 1.f / 16.f;
	x = column * inc;
	y = 1 - (row * inc) - inc;

    	glEnable(GL_TEXTURE_2D);
	glDisable(GL_DEPTH_TEST);
	glEnable(GL_BLEND);
	glBlendFunc(GL_ONE, GL_ONE_MINUS_DST_COLOR);
	glBindTexture(GL_TEXTURE_2D, pic_font.tex);

	glEnableClientState(GL_VERTEX_ARRAY);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);

//	glVertexPointer(2, GL_FLOAT, sizeof(struct point), p->vertices);
//	LOGGLE(">>glVertexPointer");

//	glTexCoordPointer(2, GL_FLOAT, sizeof(struct point), pic_tex);
//	LOGGLE("glTexCoordPointer");

	glBegin(GL_QUADS);
		glTexCoord2f( x,       y + inc);
		glVertex3f( 0.f, 1.f, 0.f);

		glTexCoord2f( x + inc, y + inc);
		glVertex3f( 1.f, 1.f, 0.f);

		glTexCoord2f( x + inc, y);
		glVertex3f( 1.f, 0.f, 0.f);

		glTexCoord2f( x,       y);
		glVertex3f( 0.f, 0.f, 0.f);
	glEnd();

	LOGGLE("draw_square draw arrays");

	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	glDisableClientState(GL_VERTEX_ARRAY);
	glDisable(GL_TEXTURE_2D);
	glDisable(GL_BLEND);
	glEnable(GL_DEPTH_TEST);

}

void draw_str(const char* s)
{
	float shift = 0;
	while (*s)
	{
		if (*s == '\n')
		{
			glTranslatef(-shift, 1.0f, 0);
			shift = 0;
		}
		else
		{
			draw_char(*s);
			glTranslatef(0.75f, 0, 0);
			shift += 0.75f;
		}
		s++;
	}
}

void app_render(long tick, int w, int h)
{
	char s[1024];
	int i, n;
	float shift_y = (float)map.img.height / (float)map.img.width;

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glClearColor(0, 0, 0, 0);
	glShadeModel(GL_SMOOTH);

	gl_set_terminal_mode();
	draw_lines();
	strcpy(s, "test");
	glScaled(20, 20, 0);
	draw_str(s);

	gl_set_map_mode();

	glLoadIdentity();
	glScaled(scale, -scale, 0);
	glTranslated(scr_offset.x, scr_offset.y, 0);

	glColor3f(1, 1, 1);

	if (tiles[0].tex != 0)
	{
		for (i = 0; i < n_tiles; i++)
		{
			pic_draw(&tiles[i]);
			glTranslatef(1, 0, 0);
			if (i % 4 == 3)
			{
				glTranslatef(-4, shift_y, 0);
			}
		}
	}

	glLoadIdentity();
	glScaled(scale, -scale, 0);
	glTranslated(scr_offset.x, scr_offset.y, 0);

	glPushMatrix();
	glScaled(1/scale, -1/scale, 0);
	glScaled(0.04, 0.04, 0);
	draw_str("0");
	glPopMatrix();

	glPushMatrix();
	for (i = 0; i < 4; i++)
	{
		glPushMatrix();
		glTranslated(1, 0, 0);
		glScaled(1/scale, -1/scale, 0);
		glScaled(0.04, 0.04, 0);
		draw_str("1");
		glPopMatrix();
		glTranslated(1, 0, 0);
	}
	glPopMatrix();

	// mouse_sc
/*	glPushMatrix();
	glTranslated(mouse_sc.x + 0.1 / scale, mouse_sc.y + 0.1 / scale, 0);
	glScaled(1/scale, -1/scale, 0);
	glScaled(0.04, 0.04, 0);
	n = sprintf(s, "%+4.3f,%+4.3f", mouse_sc.x, mouse_sc.y);
	glPushMatrix();
	draw_str(s);
	glPopMatrix();
	glTranslated(0, 1.0, 0);
	n = sprintf(s, "%+4.0f,%+4.0f", mouse_px.x, mouse_px.y);
	draw_str(s);
	glPopMatrix();
*/
	glPushMatrix();
	glColor3b(0, 80, 80);
	glTranslated(-scr_offset.x, -scr_offset.y, 0);
	glScaled(1.0/scale, -1.0/scale, 0);
	glScaled(0.04, 0.04, 0);
	glTranslated(15.0, 15.0, 0);

	n = sprintf(s, 
		"mouse_px:   %+6.0f,%+6.0f\n"
		"mouse_sc:   %+4.3f,%+4.3f\n"
		"map_px:     %+6.0f,%+6.0f\n"
		,
		mouse_px.x, mouse_px.y,
		mouse_sc.x, mouse_sc.y,
		map_px.x, map_px.y
		);

	draw_str(s);
	glPopMatrix();

	glLoadIdentity();
	glScaled(scale, -scale, 0);
	glTranslated(scr_offset.x, scr_offset.y, 0);
	draw_utm_grid();
	draw_lines();
//	draw_test_vectors();
}

static void
recalc_tfw()
{
	struct point utm_delta, px_delta;
	double utm_dist, px_dist;

	utm_delta.x = markers[1].utm.x - markers[0].utm.x;
	utm_delta.y = markers[1].utm.y - markers[0].utm.y;
	utm_dist = sqrt((utm_delta.x * utm_delta.x) + (utm_delta.y * utm_delta.y));

	px_delta.x = markers[1].px.x - markers[0].px.x;
	px_delta.y = markers[1].px.y - markers[0].px.y;
	px_dist = sqrt((px_delta.x * px_delta.x) + (px_delta.y * px_delta.y));

	tfw.pixel_size_x = utm_dist / px_dist;
		
	tfw.rot_x = tfw.pixel_size_x * px_delta.y / px_delta.x;	

	utm_delta.x = markers[1].utm.x - markers[2].utm.x;
	utm_delta.y = markers[1].utm.y - markers[2].utm.y;
	utm_dist = sqrt((utm_delta.x * utm_delta.x) + (utm_delta.y * utm_delta.y));

	px_delta.x = markers[1].px.x - markers[2].px.x;
	px_delta.y = markers[1].px.y - markers[2].px.y;
	px_dist = sqrt((px_delta.x * px_delta.x) + (px_delta.y * px_delta.y));

	tfw.pixel_size_y = -(utm_dist / px_dist);
	tfw.rot_y = tfw.pixel_size_y * px_delta.x / px_delta.y;	

	tfw.upper_left_x = markers[0].utm.x - markers[0].px.x * tfw.pixel_size_x;
	tfw.upper_left_y = markers[0].utm.y - markers[0].px.y * tfw.pixel_size_y;
}

static void
recalc()
{
	double aspect = (double)map.img.height / (double)map.img.width;

	if (scale < 0.1)
		scale = 0.1;

	if (scale > 8.0)
		scale = 8.0;

	divider.x = screen_px.x / (scr_se.x - scr_nw.x) * scale;
	divider.y = screen_px.y / (scr_se.y - scr_nw.y) * scale;

	mouse_sc.x = -(scr_offset.x - mouse_px.x / divider.x - scr_nw.x / scale);
	mouse_sc.y = -scr_offset.y - mouse_px.y / divider.y - scr_nw.y / scale;

	map_px.x = (mouse_sc.x) * map.img.width / 4.0;
	map_px.y = (mouse_sc.y) * map.img.height / 4.0 / aspect;
}

static void dump()
{
	int i;
	LOGW("=============== dump =========================");
	for (i = 0; i < 3; i++)
	{
		LOGI("marker[%d].utm: %4.3f, %4.3f", i, markers[i].utm.x, markers[i].utm.y);
		LOGI("marker[%d].px:  %4.3f, %4.3f", i, markers[i].px.x, markers[i].px.y);
		LOGI("marker[%d].sc:  %4.3f, %4.3f", i, markers[i].sc.x, markers[i].sc.y);
	}
	LOGI("tfw.pixel_size: %f, %f", tfw.pixel_size_x, tfw.pixel_size_y);
	LOGI("tfw.rotation:   %f, %f", tfw.rot_x, tfw.rot_y);
	LOGI("tfw.upper_left  %f, %f", tfw.upper_left_x, tfw.upper_left_y);
}

static void save_markers()
{
	int i;
	FILE* f = NULL;
	char fname[1024];
	sprintf(fname, "%s.markers", base_name);
	f = fopen(fname, "wt");
	if (f == NULL)
	{
		LOGE("Cannot open markers file: %s", fname);
		return;
	}

	for (i = 0; i < 3; i++)
	{
		fprintf(f, "marker %d: utm: %f, %f, px: %f, %f, screen: %f, %f\n",
			i + 1,
			markers[i].utm.x, markers[i].utm.y,
			markers[i].px.x, markers[i].px.y,
			markers[i].sc.x, markers[i].sc.y);
	}
	fclose(f);
	LOGI("markers saved");
}

static void save()
{
	if (tfw_write(&tfw, tfw_name) == -1)
	{
		LOGE("cannot save tfw file");
	}
	LOGI("tfw saved");

	save_markers();
}

void app_touch(int release, int x, int y)
{
	if (release == 0)
	{
		mouse_px.x = (float)x;
		mouse_px.y = (float)y;
	}

	switch (release)
	{
	case 3:
		scale -= 0.5;
		break;

	case 4:
		scale += 0.5;
		break;
	}

	recalc();

	switch (release)
	{
	case 0:
		if (mouse_down)
		{
			scr_offset.x -= mouse_down_sc.x - mouse_sc.x;
			scr_offset.y -= mouse_down_sc.y - mouse_sc.y;
		}
		break;

	case 1:
		mouse_down_sc = mouse_sc;
		mouse_down = 1;
		break;

	case 2:
		scr_offset.x -= mouse_down_sc.x - mouse_sc.x;
		scr_offset.y -= mouse_down_sc.y - mouse_sc.y;
		mouse_down = 0;
		break;
	}
}

void app_key(int key)
{
	int marker_changed = 0;

	switch (key)
	{
	case 'G':
		draw_grid = !draw_grid;
		break;
	case 'S':
		save();
		break;
	case 'R':
		tfw_read(&tfw, tfw_name);
		load_markers();
		marker_changed = 1;
		break;
	case 'D':
		dump();
		break;
	case 'J':
		scr_offset.x -= 0.1;
		break;
	case 186: // ;
		scr_offset.x += 0.1;
		break;
	case 'K':
		scr_offset.y -= 0.1;
		break;
	case 'L':
		scr_offset.y += 0.1;
		break;
	case 'I':
		scale += 0.1;
		break;
	case 'O':
		scale -= 0.1;
		break;
	case 'Q':
		g_app_alive = 0;
		break;
	case '1':
		markers[0].px = map_px;
		markers[0].sc = mouse_sc;
		marker_changed = 1;
		break;
	case '2':
		markers[1].px = map_px;
		markers[1].sc = mouse_sc;
		marker_changed = 1;
		break;
	case '3':
		markers[2].px = map_px;
		markers[2].sc = mouse_sc;
		marker_changed = 1;
		break;
	}

	recalc();

	if (marker_changed)
	{
		recalc_tfw();
		dump();
	}
}

void create_controls()
{
}

void app_deinit()
{
}
